/****************************************************************************
 *
 * Copyright 2016 Samsung Electronics All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 ****************************************************************************/
/****************************************************************************
 * include/tinyara/spi/spi_bitbang.c
 *
 *   Copyright (C) 2013 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <tinyara/config.h>
#include <debug.h>

#include <tinyara/spi/spi_bitbang.h>

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/
/* Usage ********************************************************************/
/* To use this logic, you should provide a C file that does the following:
 *
 * - Defines SPI_SETSCK and SPI_CLRSCK to set and clear the SCK signal
 * - Defines SPI_SETMOSI and SPI_CLRMOSI to set and clear the MISO signal
 * - Defines SPI_GETMISO to sample the MISO state
 * - Defines SPI_PERBIT_NSEC which is the minimum time to transfer one bit.
 *   This determines the maximum frequency.
 * - Other configuration options:
 *   SPI_BITBAND_LOOPSPERMSEC - Delay loop calibration
 *   SPI_BITBANG_DISABLEMODE0 - Define to disable Mode 0 support
 *   SPI_BITBANG_DISABLEMODE1 - Define to disable Mode 1 support
 *   SPI_BITBANG_DISABLEMODE2 - Define to disable Mode 2 support
 *   SPI_BITBANG_DISABLEMODE3 - Define to disable Mode 3 support
 * - Provide implementations of spi_select(), spi_status(), and spi_cmddata().
 * - Then include this file
 * - Provide an initialization function that initializes the GPIO pins used
 *   in the bit bang interface and calls spi_create_bitbang().
 */

/****************************************************************************
 * Private Types
 ****************************************************************************/

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static void spi_delay(uint32_t holdtime);
static void spi_select(FAR struct spi_bitbang_s *priv, enum spi_dev_e devid, bool selected);
static uint32_t spi_setfrequency(FAR struct spi_bitbang_s *priv, uint32_t frequency);
static void spi_setmode(FAR struct spi_bitbang_s *priv, enum spi_mode_e mode);
#ifndef SPI_BITBANG_DISABLEMODE0
static uint16_t spi_bitexchange0(uint16_t dataout, uint32_t holdtime);
#endif
#ifndef SPI_BITBANG_DISABLEMODE1
static uint16_t spi_bitexchange1(uint16_t dataout, uint32_t holdtime);
#endif
#ifndef SPI_BITBANG_DISABLEMODE2
static uint16_t spi_bitexchange2(uint16_t dataout, uint32_t holdtime);
#endif
#ifndef SPI_BITBANG_DISABLEMODE3
static uint16_t spi_bitexchange3(uint16_t dataout, uint32_t holdtime);
#endif
static uint16_t spi_exchange(FAR struct spi_bitbang_s *priv, uint16_t dataout);
static uint8_t spi_status(FAR struct spi_bitbang_s *priv, enum spi_dev_e devid);
#ifdef CONFIG_SPI_CMDDATA
static int spi_cmddata(FAR struct spi_bitbang_s *priv, enum spi_dev_e devid, bool cmd);
#endif

/****************************************************************************
 * Private Data
 ****************************************************************************/

static const struct spi_bitbang_ops_s g_spiops = {
	spi_select,					/* select */
	spi_setfrequency,			/* setfrequency */
	spi_setmode,				/* setmode */
	spi_exchange,				/* exchange */
	spi_status,					/* status */
#ifdef CONFIG_SPI_CMDDATA
	spi_cmddata,				/* cmddata */
#endif
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/
/****************************************************************************
 * Name: spi_delay
 *
 * Description:
 *   Delay for a specified number of loops
 *
 * Input Parameters:
 *   count - The number of loops
 *
 * Returned Value:
 *   Returns the actual frequency selected
 *
 ****************************************************************************/

static void spi_delay(uint32_t holdtime)
{
	volatile int i;

	for (i = 0; i < holdtime; i++) ;
}

/****************************************************************************
 * Name: spi_setfrequency
 *
 * Description:
 *   Set the SPI frequency.
 *
 * Input Parameters:
 *   dev -       Device-specific state data
 *   frequency - The SPI frequency requested
 *
 * Returned Value:
 *   Returns the actual frequency selected
 *
 ****************************************************************************/

static uint32_t spi_setfrequency(FAR struct spi_bitbang_s *priv, uint32_t frequency)
{
	uint32_t pnsec;

	/* SPI frequency cannot be precisely controlled with a bit-bang interface.
	 * Freqency corresponds to delay in toggle the SPI clock line:  Set high,
	 * wait, set low, wait, set high, wait, etc.
	 *
	 * Here we calcalute the half period of the frequency in nanoseconds (i.e.,
	 * the amount of time that the clock should remain in the high or low state).
	 *
	 *   frequency = psec / 1 sec                       psec = full period in seconds
	 *   psec      = 1 sec / frequency
	 *   pnsec     = 1000000000 nsec / (2 * frequency)  pnsec = full period in nsec
	 *
	 * As examples:
	 * 1) frequency = 400KHz; SPI_PERBIT_NSEC = 100
	 *    pnsec     = (2500 - 100) / 2 = 1200
	 * 2) frequency = 20MHz;  SPI_PERBIT_NSEC = 100
	 *    pnsec     = (50 - 100( / 2 -> 0
	 */

	pnsec = (1000000000ul + (frequency >> 1)) / frequency;

	/* Minus the bit transfer overhead */

	if (pnsec > SPI_PERBIT_NSEC) {
		pnsec -= SPI_PERBIT_NSEC;
	} else {
		pnsec = 0;
	}

	/* The hold time in nanoseconds is then half this */

	pnsec = (pnsec + 1) >> 1;

	/* But what we really want is the hold time in loop counts. We know that
	 * SPI_BITBAND_LOOPSPERMSEC is the number of times through a delay loop
	 * to get 1 millisecond.
	 *
	 * SPI_BITBAND_LOOPSPERMSEC / 1000000 is then the number of counts
	 * to get 1 nanosecond.  In reality, this is a number less than zero.  But
	 * then we can use this to calculate:
	 *
	 * holdtime loops/hold = pnsec nsec/hold * (SPI_BITBAND_LOOPSPERMSEC / 1000000) loops/nsec
	 *
	 * As examples:
	 * 1) frequency = 400KHz; SPI_PERBIT_NSEC = 100; pnsec = 1200;
	 *    SPI_BITBAND_LOOPSPERMSEC = 5000
	 *    holdtime  = (1200 * 5000 + 500000) / 1000000 = 6
	 * 2) frequency = 20MHz;  SPI_PERBIT_NSEC = 100; pnsec = 0;
	 *    SPI_BITBAND_LOOPSPERMSEC = 5000
	 *    holdtime  = (0 * 5000 + 500000) / 1000000 = 0
	 */

	priv->holdtime = (pnsec * SPI_BITBAND_LOOPSPERMSEC + 500000) / 1000000;

	/* Let's do our best to calculate the actual frequency
	 *
	 * As examples:
	 * 1) frequency = 400KHz; SPI_PERBIT_NSEC = 100;
	 *    SPI_BITBAND_LOOPSPERMSEC = 5000; holdtime = 6
	 *    pnsec     = 2 * 1000000 * 6 / 5000 + 100 = 2500 nsec
	 *    frequency = 400KHz
	 * 2) frequency = 20MHz;  SPI_PERBIT_NSEC = 100; holdtime = 0
	 *    SPI_BITBAND_LOOPSPERMSEC = 5000; holdtime = 0
	 *    pnsec     = 2 * 0 * 6 / 5000 + 100 = 100 nsec
	 *    frequency = 10MHz
	 */

	pnsec = 2 * 1000000 * priv->holdtime / SPI_BITBAND_LOOPSPERMSEC + SPI_PERBIT_NSEC;
	frequency = 1000000000ul / pnsec;
	return frequency;
}

/****************************************************************************
 * Name: spi_setmode
 *
 * Description:
 *   Select the current SPI mode
 *
 * Input Parameters:
 *   dev  - Device-specific state data
 *   mode - the new SPI mode
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void spi_setmode(FAR struct spi_bitbang_s *priv, enum spi_mode_e mode)
{
	spivdbg("mode=%d\n", mode);

	switch (mode) {
	case SPIDEV_MODE0:			/* CPOL=0; CPHA=0 */
#ifndef SPI_BITBANG_DISABLEMODE0
		SPI_CLRSCK;				/* Resting level of the clock is low */
		priv->exchange = spi_bitexchange0;
#else
		DEBUGPANIC();
#endif
		break;

	case SPIDEV_MODE1:			/* CPOL=0; CPHA=1 */
#ifndef SPI_BITBANG_DISABLEMODE1
		SPI_CLRSCK;				/* Resting level of the clock is low */
		priv->exchange = spi_bitexchange1;
#else
		DEBUGPANIC();
#endif
		break;

	case SPIDEV_MODE2:			/* CPOL=1; CPHA=0 */
#ifndef SPI_BITBANG_DISABLEMODE2
		SPI_SETSCK;				/* Resting level of the clock is high */
		priv->exchange = spi_bitexchange2;
#else
		DEBUGPANIC();
#endif
		break;

	case SPIDEV_MODE3:			/* CPOL=1; CPHA=1 */
#ifndef SPI_BITBANG_DISABLEMODE3
		SPI_SETSCK;				/* Resting level of the clock is high */
		priv->exchange = spi_bitexchange3;
#else
		DEBUGPANIC();
#endif
		break;

	default:
		DEBUGPANIC();
		break;
	}
}

/****************************************************************************
 * Name: spi_bitexchange0
 *
 * Description:
 *   Exchange one bit in mode 0
 *
 *   MODE 0: CPOL=0 and CPHA = 0
 *   The base value of the clock is zero.  Data is captured on the clock's
 *   rising edge and data is propagated on the falling edge (high->low
 *   transition).
 *
 * /CS --+
 *       |
 *       +-----------------------------------------------------------------
 *       <-hold time-> <-hold time-><-hold time-><-hold time-><-hold time->
 *                     +------------+            +------------+
 *                     |            |            |            |
 * SCLK ---------------+            +------------+            +------------
 *       `- Set MOSI    |            `- Set MOSI  |            `- Set MOSI
 *                      `- Sample MISO            `- Sample MISO
 *
 * MISO  /-------------X-----------\/------------X-------------\/-----------
 * MOSO  \-------------X-----------/\------------X-------------/\-----------
 *
 * Input Parameters:
 *   dev  - Device-specific state data
 *   lock - true: Lock spi bus, false: unlock SPI bus
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifndef SPI_BITBANG_DISABLEMODE0
static uint16_t spi_bitexchange0(uint16_t dataout, uint32_t holdtime)
{
	uint16_t datain;

	/* Here the clock is is in the resting set (low).  Set MOSI output and wait
	 * for the hold time
	 */

	if (dataout != 0) {
		SPI_SETMOSI;
	} else {
		SPI_CLRMOSI;
	}

	spi_delay(holdtime);

	/* Set the clock high and sample MISO */

	SPI_SETSCK;
	datain = (uint16_t)SPI_GETMISO;

	/* Wait the required amount of hold time then put the clock back in the
	 * resting state.
	 */

	spi_delay(holdtime);
	SPI_CLRSCK;

	return datain;
}
#endif

/****************************************************************************
 * Name: spi_bitexchange1
 *
 * Description:
 *   Exchange one bit in mode 1
 *
 *   MODE 1: CPOL=0 and CPHA = 1
 *   The base value of the clock is zero.  Data is captured on the clock's
 *   falling edge and data is propagated on the rising edge
 *
 * /CS --+
 *       |
 *       +-----------------------------------------------------------------
 *       <-hold time-> <-hold time-><-hold time-><-hold time-><-hold time->
 *       +------------+            +------------+
 *       |            |            |            |
 * SCLK -+            +------------+            +------------
 *        `- Set MOSI  |            `- Set MOSI  |
 *                      `- Sample MISO            `- Sample MISO
 *
 * MISO   /-----------X------------\/-----------X-------------\/------------
 * MOSO   \-----------X------------/\-----------X-------------/\------------
 *
 * Input Parameters:
 *   dev  - Device-specific state data
 *   lock - true: Lock spi bus, false: unlock SPI bus
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifndef SPI_BITBANG_DISABLEMODE1
static uint16_t spi_bitexchange1(uint16_t dataout, uint32_t holdtime)
{
	uint16_t datain;

	/* The clock should be in the resting state (low).  Set the clock to the
	 * high state and set MOSI.
	 */

	SPI_SETSCK;
	if (dataout != 0) {
		SPI_SETMOSI;
	} else {
		SPI_CLRMOSI;
	}

	/* Wait for the required hold time then put the clock back into the
	 * resting (low) state.
	 */

	spi_delay(holdtime);
	SPI_CLRSCK;

	/* Sample MISO on the falling edge and wait for the hold time again */

	datain = (uint16_t)SPI_GETMISO;
	spi_delay(holdtime);

	return datain;
}
#endif

/****************************************************************************
 * Name: spi_bitexchange2
 *
 * Description:
 *   Exchange one bit in mode 2
 *
 *   MODE 2: CPOL=1 and CPHA = 0
 *     The base value of the clock is one.  Data is captured on the clock's
 *     falling edge and data is propagated on the rising edge.
 *
 * /CS --+
 *       |
 *       +-----------------------------------------------------------------
 *       <-hold time-> <-hold time-><-hold time-><-hold time-><-hold time->
 *      ---------------+            +------------+            +------------
 *                     |            |            |            |
 * SCLK                +------------+            +------------+
 *       `- Set MOSI    |            `- Set MOSI  |             `- Set MOSI
 *                       `- Sample MISO            `- Sample MISO
 *
 * MISO  /-------------X------------\/-----------X-------------\/-----------
 * MOSO  \-------------X------------/\-----------X-------------/\-----------
 *
 * Input Parameters:
 *   dev  - Device-specific state data
 *   lock - true: Lock spi bus, false: unlock SPI bus
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifndef SPI_BITBANG_DISABLEMODE2
static uint16_t spi_bitexchange2(uint16_t dataout, uint32_t holdtime)
{
	uint16_t datain;

	/* Here the clock is is in the resting set (high).  Set MOSI output and wait
	 * for the hold time
	 */

	if (dataout != 0) {
		SPI_SETMOSI;
	} else {
		SPI_CLRMOSI;
	}

	spi_delay(holdtime);

	/* Set the clock low and sample MISO */

	SPI_CLRSCK;
	datain = (uint16_t)SPI_GETMISO;

	/* Wait the required amount of hold time then put the clock back in the
	 * resting state (high).
	 */

	spi_delay(holdtime);
	SPI_SETSCK;

	return datain;
}
#endif

/****************************************************************************
 * Name: spi_bitexchange3
 *
 * Description:
 *   Exchange one bit in mode 3
 *
 *   MODE 3: CPOL=1 and CPHA = 1
 *     The base value of the clock is one.  Data is captured on the clock's
 *     rising edge and data is propagated on the falling edge.
 *
 * /CS --+
 *       |
 *       +-----------------------------------------------------------------
 *       <-hold time-> <-hold time-><-hold time-><-hold time-><-hold time->
 *      -+            +------------+            +------------
 *       |            |            |            |
 * SCLK  +------------+            +------------+
 *        ` Set MOSI   |            `- Set MOSI  |
 *                      `- Sample MISO            `- Sample MISO
 *
 * MISO   /-----------X------------\/-----------X-------------\/------------
 * MOSO   \-----------X------------/\-----------X-------------/\------------
 *
 * Input Parameters:
 *   dev  - Device-specific state data
 *   lock - true: Lock spi bus, false: unlock SPI bus
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifndef SPI_BITBANG_DISABLEMODE3
static uint16_t spi_bitexchange3(uint16_t dataout, uint32_t holdtime)
{
	uint16_t datain;

	/* The clock should be in the resting state (high).  Set the clock to the
	 * low state and set MOSI.
	 */

	SPI_CLRSCK;					/* Clock transition before setting MOSI */
	if (dataout != 0) {
		SPI_SETMOSI;			/* Set MOSI if the bit is set */
	} else {
		SPI_CLRMOSI;			/* Clear MOSI if the bit is not set */
	}

	/* Wait for the required hold time then put the clock back into the
	 * resting (high) state.
	 */

	spi_delay(holdtime);
	SPI_SETSCK;

	/* Sample MISO on the rising edge and wait for the hold time again */

	datain = (uint16_t)SPI_GETMISO;
	spi_delay(holdtime);

	return datain;
}
#endif

/****************************************************************************
 * Name: spi_exchange
 *
 * Description:
 *   Exahange on word of data on SPI
 *
 * Input Parameters:
 *   priv     - Device-specific state data
 *   data     - The TX data to be exchanged with the slave
 *
 * Returned Value:
 *   The RX data received from the slave
 *
 ****************************************************************************/

#ifdef CONFIG_SPI_BITBANG_VARWIDTH
static uint16_t spi_exchange(FAR struct spi_bitbang_s *priv, uint16_t dataout)
{
	bitexchange_t exchange = priv->exchange;
	uint32_t holdtime = priv->holdtime;
	uint16_t datain;
	uint16_t bit;
	int shift;

	/* Transfer each bit.  This might be better done with straight-line
	 * logic because the loop overhead will limit our maximum transfer
	 * rate.
	 */

	shift = priv->nbits - 1;
	for (bit = 1 << shift; bit != 0; bit >>= 1) {
		/* Shift to make space for the next, less significant bit.
		 * Then exchange bits with the slave an OR in the new, returned
		 * bit.
		 */

		datain <<= 1;
		datain |= exchange(!!(dataout & bit), holdtime);
	}

	return datain;
}

#else
static uint16_t spi_exchange(FAR struct spi_bitbang_s *priv, uint16_t dataout)
{
	bitexchange_t exchange = priv->exchange;
	uint32_t holdtime = priv->holdtime;
	uint8_t datain;

	/* Transfer each bit.  This is better done with straight-line logic
	 * when possible because the loop overhead will limit our maximum transfer
	 * rate.
	 */

	/* Exchange bit 7 with the slave */

	datain = priv->exchange(dataout & (1 << 7), holdtime);

	/* Exchange bit 6 with the slave */

	datain <<= 1;
	datain |= priv->exchange(dataout & (1 << 6), holdtime);

	/* Exchange bit 5 with the slave */

	datain <<= 1;
	datain |= priv->exchange(dataout & (1 << 5), holdtime);

	/* Exchange bit 4 with the slave */

	datain <<= 1;
	datain |= priv->exchange(dataout & (1 << 4), holdtime);

	/* Exchange bit 3 with the slave */

	datain <<= 1;
	datain |= priv->exchange(dataout & (1 << 3), holdtime);

	/* Exchange bit 2 with the slave */

	datain <<= 1;
	datain |= priv->exchange(dataout & (1 << 2), holdtime);

	/* Exchange bit 1 with the slave */

	datain <<= 1;
	datain |= priv->exchange(dataout & (1 << 1), holdtime);

	/* Exchange bit 0 with the slave */

	datain <<= 1;
	datain |= priv->exchange(dataout & (1 << 0), holdtime);

	return datain;
}
#endif
